import pandas as pd
from os import path as os_path
from .ColumnAttr import ColumnAttr
from abc import ABC, abstractmethod
import os
from typing import Dict, Any
import warnings
import json
import re
import time

warnings.filterwarnings('ignore', category=UserWarning, module='pandas')

'''
    第1行 中文名(自我描述的名字，主要用于描述)
    第2行 类型: int, string, bool, array, object, float
    第3行 属性: s: 服务器导出, c: 客户端导出, k: 主键(k1, k2, k3), 使用 ',' 分割
    第4行 英文名(只能是字母, 数字, 下划线, 不能有空格, 不能有特殊字符, 不能有中文)
    第5行... 内容
'''

# 中文名所在行
LOCAL_NAME_ROW = 0
# 每列类型所在行
COL_TYPE_ROW = 1
# 每列属性所在行
COL_ATTR_ROW = 2
# 每列名称所在行
COL_NAME_ROW = 3
# 表头行数
HEADER_ROW_COUNT = 4

class Exporter(ABC):
    '''
        构造函数
        source_dir: 源目录
        dest_dir: 目标目录
    '''
    def __init__(self, source: str = None, dest: str = None, target: str=None, lang: str=None):
        self._source = source
        self._dest = dest
        self._target = target
        self._lang = lang
        self._meta = {}

    @property
    def source(self):
        return self._source

    @source.setter
    def source(self, value):
        self._source = value

    @property
    def dest(self):
        return self._dest

    @dest.setter
    def dest(self, value):
        self._dest = value

    @property
    def target(self):
        return self._target

    @target.setter
    def target(self, value):
        self._target = value

    @property
    def lang(self):
        return self._lang

    @lang.setter
    def lang(self, value):
        self._lang = value

    @property
    def meta(self):
        return self._meta
    
    @property
    def force(self):
        return self._force

    @force.setter
    def force(self, value):
        self._force = value
    
    @property
    def prefix(self):
        return self._prefix
    
    @prefix.setter
    def prefix(self, value):
        self._prefix = value

    @property
    def suffix(self):
        return self._suffix
    
    @suffix.setter
    def suffix(self, value):
        self._suffix = value

    @property
    def case(self):
        return self._case
    
    @case.setter
    def case(self, value):
        self._case = value

    def get_col_type(self, file_name, sheet_name, col_name):
        return self.meta.get(file_name, {}).get(sheet_name, {}).get('col_types', {}).get(col_name)

    def get_col_attr(self, file_name, sheet_name, col_name):
        return self.meta.get(file_name, {}).get(sheet_name, {}).get('col_attrs', {}).get(col_name)

    def get_key_columns(self, file_name, sheet_name):
        return self.meta.get(file_name, {}).get(sheet_name, {}).get('key_columns', [])

    def get_col_names(self, file_name, sheet_name):
        return self.meta.get(file_name, {}).get(sheet_name, {}).get('col_names', [])

    def _create_meta(self, file_name, sheet_name, col_types, col_attrs, key_columns, col_names):
        if file_name not in self.meta:
            self.meta[file_name] = {}

        self.meta[file_name][sheet_name] = {
            # 列类型，excel: { sheet: { col: type } }
            'col_types': col_types,
            # 列属性，excel: { sheet: { col: attr } }
            'col_attrs': col_attrs,
            # 主键，excel: { sheet: [col_name1, col_name2, col_name3] }
            'key_columns': key_columns,
            # 列名称，excel: { sheet: [col_name1, col_name2, col_name3] }
            'col_names': col_names,
            'key_count': len(key_columns),
        }

    '''
        导出文件
        source_file: 源文件
        output_file: 输出文件路径|目录
        lang: 目标语言
    '''
    def export(self):
        if not os_path.exists(self.source):
            raise ValueError(f"源文件不存在: {self.source}")

        if os_path.isfile(self.source):
            if not self.source.endswith('.xlsx'):
                raise ValueError(f"源文件类型错误: {self.source}")
            self.export_file(self.source, self.dest)
        else:
            self.export_dir(self.source, self.dest)

    def export_file(self, source_file: str, output: str):
        if not os_path.exists(source_file):
            raise ValueError(f"文件不存在: {source_file}")

        file_name = os_path.basename(source_file).split('.')[0]
        export_file_path = os.path.join(output, self.get_filename(file_name))
        if not self.force and os_path.exists(export_file_path) and os_path.getmtime(export_file_path) > os_path.getmtime(source_file):
            print(f"文件 {source_file} 没有更新，跳过导出")
            return

        # 如果文件名包含 '-', 则 '-' 左边表示文件属性
        file_attr = len(file_name.split('-')) > 2 and file_name.split('-')[0]
        if file_attr:
            if 'c' in file_attr and self.target != 'client':
                print(f"文件 {source_file} 属性为 c, 只导出到客户端")
                return
            elif 's' in file_attr and self.target != 'server':
                print(f"文件 {source_file} 属性为 s, 只导出到服务端")
                return

        start_time = time.time()
        df = pd.read_excel(source_file,
            sheet_name=None,
            header=None,
            na_values=[],
            keep_default_na=False,
            na_filter=False
        )

        result = {}
        for sheet_name, sheet in df.items():
            # 如果文件名包含 '-', 则 '-' 左边表示文件属性
            sheet_attr = len(sheet_name.split('-')) > 2 and sheet_name.split('-')[0]
            if sheet_attr:
                if 'c' in sheet_attr and self.target != 'client':
                    print(f"文件 {source_file} 的表格 {sheet_name} 属性为 c, 只导出到客户端")
                    continue
                elif 's' in sheet_attr and self.target != 'server':
                    print(f"文件 {source_file} 的表格 {sheet_name} 属性为 s, 只导出到服务端")
                    continue

            data = self.process_sheet(file_name, sheet_name, sheet)
            result[sheet_name] = data

        os.makedirs(os_path.dirname(export_file_path), exist_ok=True)
        self.write_output(result, export_file_path, file_name)
        print(f"导出文件配置完成, {source_file} -> {export_file_path}, 耗时: {time.time() - start_time:.2f}s")

    def compile_col_type(self, col_type: str):
        if col_type == "int":
            return "int"
        elif col_type == "float":
            return "float"
        elif col_type == "bool" or col_type == "boolean":
            return "bool"
        elif col_type == "string":
            return "string"
        elif col_type == "array":
            return "array"
        elif col_type == "object":
            return "object"

        raise ValueError(f"未知类型: {col_type}")

    '''
        编译元数据
    '''
    def compile_meta(self, file_name, sheet_name, sheet: pd.DataFrame):
        header_info = sheet.iloc[:HEADER_ROW_COUNT].values.tolist()
        sheet.columns = header_info[COL_NAME_ROW]
        column_types = header_info[COL_TYPE_ROW]
        column_attrs = header_info[COL_ATTR_ROW]
        column_names = header_info[COL_NAME_ROW]

        attrs = {}
        types = {}
        for key_idex in range(len(column_names)):
            col_name = column_names[key_idex]
            if col_name == "":
                continue

            if col_name in attrs:
                raise ValueError(f"列名重复: {col_name}")

            attrs[col_name] = ColumnAttr(col_name, column_attrs[key_idex])
            types[col_name] = self.compile_col_type(column_types[key_idex])

        # 效验属性，必须要有 key 属性，并且不能重复 同样层级的key
        key_attrs = {}
        for col_name, attr in attrs.items():
            if attr.key is not None:
                if attr.key in key_attrs:
                    raise ValueError(f"key 重复: {attr.key}")

                key_attrs[attr.key] = col_name

        key_columns = []
        for i in range(1, len(key_attrs) + 1):
            key_columns.append(key_attrs[i])

        self._create_meta(file_name, sheet_name, types, attrs, key_columns, column_names)
        return attrs, types, key_columns

    def compile_data(self, sheet, col_attrs, col_types):
        data = sheet.iloc[HEADER_ROW_COUNT:, ~sheet.columns.duplicated()].reset_index(drop=True).to_dict(orient='records')
        i = 0

        # 删除空值
        for row in data:
            row_copy = dict(row)
            for key, value in row_copy.items():
                # "#REF!" 是 excel 的错误值
                if value is None or value == "" or key == "" or value == '#REF!':
                    # 值为空，或key为空，删除
                    del row[key]
                    continue

                if key not in col_types:
                    # 没有定义类型，不用导出
                    del row[key]
                    continue

                if not col_attrs[key].need_export(self.target):
                    # 不需要导出，删除
                    del row[key]
                    continue

        # 删除空行
        while i < len(data):
            if not data[i]:  # 如果字典为空
                del data[i]
            else:
                i += 1

        return data

    def process_sheet(self, file_name: str, sheet_name: str, sheet: pd.DataFrame) -> Dict[str|int, Any]:
        col_attrs, col_types, key_columns = self.compile_meta(file_name, sheet_name, sheet)

        key_count = len(key_columns)

        data = self.compile_data(sheet, col_attrs, col_types)
        result = {}
        for row in data:
            if len(row) == 0:
                # 跳过空行
                continue

            # 处理有效的行数据
            current_level = result
            skip = False
            for key_index in range(key_count):
                key_name = key_columns[key_index]
                if key_name not in row:
                    # 没有设置所有主键的数据，不导出
                    skip = True
                    break

                key_value = row[key_name]
                if key_index == key_count - 1:
                    # 最后一个键，存储整行数据
                    current_level[key_value] = row
                    continue

                # 不是最后一个键，创建或获取下一级字典
                if key_value not in current_level:
                    current_level[key_value] = {}

                current_level = current_level[key_value]

            if skip:
                continue

            for col_name, value in row.items():
                col_type = col_types[col_name]
                if col_type == "int":
                    row[col_name] = int(value)
                elif col_type == "float":
                    row[col_name] = float(value)
                elif col_type == "bool":
                    svalue = str(value)
                    if svalue == "true" or svalue == "1":
                        row[col_name] = True
                    elif svalue == "false" or svalue == "0":
                        row[col_name] = False
                    else:
                        raise ValueError(f"bool 类型错误: {value}, {type(value)}")
                elif col_type == "array":
                    row[col_name] = json.loads(f"[{value}]")
                elif col_type == "object":
                    fixed_json_str = re.sub(r'([\w]+)(\s*:\s*)', r'"\1"\2', value)
                    row[col_name] = json.loads(f"{{{fixed_json_str}}}")

        return result

    def export_dir(self, source: str, dest: str):
        for dirpath, dirnames, files in os.walk(source):
            for file in files:
                if file.endswith('.xlsx') and not file.startswith(("~$", ".")):
                    source_file = os_path.join(dirpath, file)
                    relative_path = os_path.relpath(source_file, source)
                    output_file_dir = os_path.dirname(os_path.join(dest, relative_path))
                    self.export_file(source_file, output_file_dir)

    @abstractmethod
    def write_output(self, data: Dict[str, Dict[str, Any]], output_path: str, file_name: str):
        pass

    def get_filename(self, file_name: str):
        final_file_name = file_name
        if self.case == 'camel':
            # 第一个字母小写，其他单词大写
            final_file_name = re.sub(r'(?<!^)(?=[A-Z])', '_', file_name).lower()    # 先转换为蛇形命名
            words = file_name.split('_')
            final_file_name = words[0][0].lower() + words[0][1:] + ''.join(word.capitalize() for word in words[1:])
        elif self.case == 'pascal' or self.case == 'camel_upper':
            # 大驼峰命名
            file_name = re.sub(r'(?<!^)(?=[A-Z])', '_', file_name).lower()  # 先转换为蛇形命名
            words = file_name.split('_')
            final_file_name = ''.join(word.capitalize() for word in words)
        elif self.case == 'snake':
            # 蛇形命名
            final_file_name = re.sub(r'(?<!^)(?=[A-Z])', '_', file_name).lower()

        print(final_file_name, self.case, file_name)
        return f"{self.prefix}{final_file_name}{self.suffix}.{self.file_ext()}"

    @abstractmethod
    def file_ext(self):
        pass